<?php

class Entity
{
	protected
	$validate = array(
	);
	
	public
	$name = '';
	
	protected
	$data = array();
	
	public
	function
	exists()
	{
		return false;
	}

	////////////////////////////////////////////////////////////////////////////
	/////  МЕТОДЫ ВАЛИДАЦИИ  ///////////////////////////////////////////////////

	function equalTo($data, $fieldName, $params)
    {
        return isset($this->data[$this->name][$fieldName]) && $data === $this->data[$this->name][$fieldName];
    }

    function customEreg($data, $regexp, $params)
    {
        if (mb_strpos($regexp, '^') === false) {
            $regexp = '.*'.$regexp;
        }
        if (mb_strpos($regexp, '$')+1 != mb_strlen($regexp)) {
            $regexp .= '.*';
        }
        mb_internal_encoding(DEFAULT_CHARSET);
        mb_regex_encoding(DEFAULT_CHARSET);
        return mb_ereg_match($regexp, $data);
    }

    function lengthBetween($data, $min, $max)
    {
        mb_internal_encoding(DEFAULT_CHARSET);
        $length = mb_strlen($data);
        return $length >= $min && $length <= $max;
    }

    function minLength($data, $value)
    {
        mb_internal_encoding(DEFAULT_CHARSET);
        return mb_strlen($data) >= $value;
    }

    function maxLength($data, $value)
    {
        mb_internal_encoding(DEFAULT_CHARSET);
        return mb_strlen($data) <= $value;
    }

    function enum($data, $list)
    {
        return in_array($data, $list);
    }

    function sqlDate($data)
    {
        return Date::valid_sql_date($data);
    }

    function sqlDateTime($data)
    {
        return Date::valid_sql_datetime($data);
    }
   
	function comparison($check1, $operator = null, $check2 = null) 
	{
		if (is_array($check1)) 
		{
			extract($check1, EXTR_OVERWRITE);
		}
		$operator = str_replace(array(' ', "\t", "\n", "\r", "\0", "\x0B"), '', strtolower($operator));

		switch ($operator) {
			case 'isgreater':
			case '>':
				if ($check1 > $check2) {
					return true;
				}
				break;
			case 'isless':
			case '<':
				if ($check1 < $check2) {
					return true;
				}
				break;
			case 'greaterorequal':
			case '>=':
				if ($check1 >= $check2) {
					return true;
				}
				break;
			case 'lessorequal':
			case '<=':
				if ($check1 <= $check2) {
					return true;
				}
				break;
			case 'equalto':
			case '==':
				if ($check1 == $check2) {
					return true;
				}
				break;
			case 'notequal':
			case '!=':
				if ($check1 != $check2) {
					return true;
				}
				break;
		}
		return false;
	}

	////////////////////////////////////////////////////////////////////////////

	/////  ВАЛИДАЦИЯ  //////////////////////////////////////////////////////////

    protected $defaultRule = array (
		'allowEmpty'  => true,
		'required'    => false,
		'rule'        => null,
		'last'        => true,
		'on'          => null
    );

	function getErrorMessage($fieldName, $ruleName)
	{
	    $validator = $this->getValidator($fieldName, $ruleName);

	    $message = '';

		if (isset($validator['message'])) {
			$message = $validator['message'];
		} elseif (is_string($ruleName)) {
			$validator['message'] = $ruleName;
		} elseif (is_numeric($ruleName) && count($ruleName) > 1) {
		    $message = $ruleName + 1;
		} else {
			$message = 'cannot_be_blank';
		}

		return $message;
	}

	function validateFieldByValidator($fieldName, $ruleName)
	{
		$data =& $this->getDataRef();
		$validator = $this->getValidator($fieldName, $ruleName);

	    $value =& $data[$fieldName];

		if (is_array($validator['rule'])) {
			$rule = $validator['rule'][0];
			unset($validator['rule'][0]);
			$ruleParams = array_merge(array($data[$fieldName]), array_values($validator['rule']));
		} else {
			$rule = $validator['rule'];
			$ruleParams = array($data[$fieldName]);
		}

		$valid = true;

		if ($rule) {
    		if (method_exists($this, $rule)) {
    			$ruleParams[] = array_diff_key($validator, $this->defaultRule);
    			$valid = call_user_func_array(array(&$this, $rule), $ruleParams);
    		} elseif (!is_array($validator['rule'])) {
    			$valid = preg_match($rule, $data[$fieldName]);
    		}
		}
		return $valid;
	}

	function getValidator($fieldName, $ruleName)
	{
	    $ruleSet = $this->getRuleSet($fieldName);
        $validator = $ruleSet[$ruleName];
    	if (!is_array($validator)) {
    		$validator = array('rule' => $validator);
    	}

    	$validator = array_merge($this->defaultRule, $validator);

    	return $validator;
	}

	function getRuleSet($fieldName)
	{
	    $ruleSet = $this->validate[$fieldName];
		if (!is_array($ruleSet) || (is_array($ruleSet) && isset($ruleSet['rule']))) {
			$ruleSet = array($ruleSet);
		}

		//$ruleSet = array_reverse($ruleSet, true);

        return $ruleSet;
	}

	function &getDataRef()
	{
		$data = array();
		if (isset($this->data[$this->name])) {
			$data =& $this->data[$this->name];
		} else {
			$data =& $this->data;
		}

		return $data;
	}

	function invalidFields($data = array())
	{
		if (empty($data)) {
			$data = $this->getDataRef();
		}

		if (empty($this->validate)) {
			return $this->validationErrors;
		}

		$exists = $this->exists();

		foreach ($this->validate as $fieldName => $ruleSet) {

		    $ruleSet = $this->getRuleSet($fieldName);
            if (array_key_exists('@dependent', $ruleSet)) {
                if (!$this->chackByDependent($ruleSet['@dependent'])) {
                    continue;
                } else {
                    unset($ruleSet['@dependent']);
                }
            }

			foreach ($ruleSet as $index => $validator) {

			    $validator = $this->getValidator($fieldName, $index);

				// Подходит ли текущее правило для данного случая
				if ((($validator['on'] == 'create') && $exists) || (($validator['on'] == 'update') && !$exists)) {
                    continue;
				}

				// В начале все хорошо, далее — череда проверок
			    $valid_flag = true;

                $stringType = true;
                $numberType = false;

                // Присуцтвует ли поле в наборе
                if (array_key_exists($fieldName, $data)) {
                    $value =& $data[$fieldName];
                    $empty = empty($data[$fieldName]) && (($numberType && ($value==='0')) || $stringType);
                    // Значение должно быть задано?
                    if ($empty) {
                        if ($validator['allowEmpty']) {
							break;
                        } else {
                            $valid_flag = false;
                        }
                    // Наконец-то проверка значения
                    } elseif (! $this->validateFieldByValidator($fieldName, $index)) {
                            $valid_flag = false;
                    }
			    } else {
                    // Поле должно быть?
                    if ($validator['required']) {
                        $valid_flag = false;
                    }
			    }

			    if (! $valid_flag) {
				    $this->invalidate($fieldName, $this->getErrorMessage($fieldName, $index));
				    if ($validator['last']) {
                        break;
				    }
			    }
			}
		}
		return $this->validationErrors;
	}
	
	function chackByDependent(&$dependent)
	{
	    $cond = true;

        $dependent = strval($dependent);

        $leadingFields = array();
        preg_match_all('/`([^`]+)`/s', $dependent, $matches, PREG_PATTERN_ORDER);
        if (isset($matches[1]) && count($matches[1])) {
            for ($i = 0; $i < count($matches[1]); $i++) {
                $leadingFields[] = $matches[1][$i];
            }
        }
        if ($leadingFields) {
            if (count(array_diff($leadingFields, array_keys($this->data[$this->name])))) {
                //throw new Exception();
                return $cond;
            }

            $knownLeadingFields = array();
            $unknownLeadingFields = array();
            $patterns = array();
            $replaces = array();

            foreach ($leadingFields as $fieldName) {
                $patterns[] = "/(`{$fieldName}`)/s";
                $replaces[] = "'".addslashes($this->data[$this->name][$fieldName])."'";
            }

            $expression = preg_replace($patterns, $replaces, $dependent);
        } else {
            $expression =& $dependent;
        }
        try {
            @eval('$cond = (bool)('.$expression.');');
        } catch (Exception $e) {}

        return $cond;
	}	
	
    /**
     * Возвращает array_assoc изменённых, относительно сохранённых, 
     * полей с текущими значениями (т. е. из БД).
     *
     * @param array $changed_fields Список проверяемых полей. Необязательно.
     *
     * @return array asoc
     */
    function 
    getChangedFields($changed_fields = array())
    {
    	return $changed_fields;
    }
	
	/*   function getChangedFields($changed_fields = array())
    {
        $from_list = count($changed_fields);
        if ($from_list) {
            $changed_fields = array_combine($changed_fields, $changed_fields);
        } else {
            $changed_fields = array();
        }
	    if ($this->exists()) {
            $clone_model = clone $this;
            $clone_model->rel();
            $old_data = $clone_model->read($from_list ? array_keys($changed_fields) : null);
            if (empty($old_data[$this->name])) {
                throw new DBException();
            }

            foreach ($old_data[$this->name] as $field_name => $old_value) {
                if (array_key_exists($field_name, $this->data[$this->name]) &&
                    $this->data[$this->name][$field_name] !== $old_value)
                {
                    $changed_fields[$field_name] = $old_value;
                } else {
                    if (array_key_exists($field_name, $changed_fields)) {
                        unset($changed_fields[$field_name]);
                    }
                }
            }
        }

        return $changed_fields;
    }
	*/
	
	
	
	
	/**
	 * Вызов событий beforeChange() и afterChange() для определённых в модели полей.
	 * Для обработки события нужно создать метод с именем вида:
	 * beforeChange_<название_поля>() или соответственно afterChange_<название_поля>().
	 *
	 * Событие beforeChange является cancellable, afterChange — нет.
	 * @todo Следует учесть, что на пути от начала вызовов событий до конца данные могут меняться.
	 */
    ///////////////////////////////////////////////////////////////////////
    protected 
    $_on_after_change_fields = array();

    protected 
    function 
    _throw_beforeChange()
    {
        $method_pref = 'beforeChange_';

        $this->_on_after_change_fields = array();

        if (! $this->exists()) 
        {
            return true;
        }

        $after_change_observable = $this->_getOnAfterChangeObservableFields();
        if (count($after_change_observable)) 
        {
            $this->_on_after_change_fields = $this->getChangedFields($after_change_observable);
        }

        $changed_fields = array();
        foreach ($this->_getOnBeforeChangeObservableFields() as $field_name) 
        {
            if (array_key_exists($field_name, $this->data[$this->name])) 
            {
                $changed_fields[] = $field_name;
            }
        }
        if (count($changed_fields)) 
        {
            foreach ($this->getChangedFields($changed_fields) as $field_name => $old_value) 
            {
                $method_name = $method_pref.$field_name;
                if (! $this->{$method_name}($this->data[$this->name][$field_name], $old_value)) 
                {
                    return false;
                }
            }
        }

        return true;
    }

    protected function _throw_afterChange()
    {
        $method_pref = 'afterChange_';

        foreach ($this->_on_after_change_fields as $field_name => $old_value) 
        {
            /**
             * Вдруг была отмена?..
             */
            if (! array_key_exists($field_name, $this->data[$this->name])) 
            {
                continue;
            }
            $method_name = $method_pref.$field_name;

            $to_dont_change_from_method = $this->data[$this->name][$field_name];
            $this->{$method_name}($to_dont_change_from_method, $old_value);
        }
    }

    protected function _getOnBeforeChangeObservableFields()
    {
        $method_pref = 'beforeChange_';
        $observable_fields = array();
        foreach (get_class_methods($this) as $method_name) 
        {
            if ((strpos($method_name, $method_pref) === 0) && (strlen($method_name) > strlen($method_pref))) 
            {
                $observable_fields[] = substr($method_name, strlen($method_pref));
            }
        }

        return $observable_fields;
    }

    protected function _getOnAfterChangeObservableFields()
    {
        $method_pref = 'afterChange_';
        $observable_fields = array();
        foreach (get_class_methods($this) as $method_name) 
        {
            if ((strpos($method_name, $method_pref) === 0) 
            	&& (strlen($method_name) > strlen($method_pref))) 
            {
                $observable_fields[] = substr($method_name, strlen($method_pref));
            }
        }

        return $observable_fields;
    }

    function beforeSave()
	{
	    if (! $this->_throw_beforeChange()) 
	    {
            return false;
	    }
	}

    function afterSave()
	{
	    $this->_throw_afterChange();
	}
}

class AuthProfile extends Entity
{
	public
	$name = 'AuthProfile';
	
	protected
	$validation = array(
		'name' => array(
			'required' => true,			
		),
		'password' => array(
			'required' => true,			
		),
		'email' => array(
			'required' => true,			
		),
		'gander' => array(
			'enum' => array('F', 'M'), 
			'required' => true,			
		),
		
	);	
	
}